// MainWindow.cs created with MonoDevelop
// User: ikex at 6:53 A 11/09/2008
//
// This is not optmised, and probably the messiest code i have ever written.
// But i havent slept in nearly 3 days and at this point i couldn't care less
// but when i wake up in the morning i'll fix it all up and make it so
// im not reusing the same code OVER AND OVER AGAIN.
//
/* ikex- 
 * FRUSTRATIONS.
 * i just spent 2 hours converting all my code to run on GTK 2.10 (for windows compat because
 * they fucking havent ported 2.12 yet.) searching for sqlite dlls, gtksourceview dlls for windows
 * old ubuntu packages for older cil wrappers for gksourceview. TO FIND OUT IT JUST CRASHES ANYWAY
 * FUCKFUCKFUCKFUCK
 * 
 * windows compat will be done when they release a 2.12 GTK# bins for windows.
 */
using System;
using System.Data;
using Gtk;
using Gdk;
using GtkSourceView;
using System.Collections;
using Mono.Data.SqliteClient;
using snippetmanager;
using System.Text.RegularExpressions;
using System.Reflection;
using System.IO;
//using System.
/// <summary>
/// Entry class is the glue that holds the Treestore and the database together
/// exposes Name, Lang, and Code.
/// </summary>
public class Entry 
{
	public string Name;
	public string Lang;
	public string Code;
	public Entry (string name, string lang, string code)
	{
		this.Name = name;
		this.Lang = lang;
		this.Code = code;
	}

}

public partial class MainWindow: Gtk.Window
{	
	private static StatusIcon trayIcon;
	static SourceLanguage language;
	static SourceView view;
	static TreeStore ts;
	Gtk.TreeIter siter;
	Gtk.TreeIter oldsiter;
	Gtk.TreeIter activeiter;
	string ActiveName = "";
	string ActiveLang = "";
	public ArrayList entries = new ArrayList();
	OnlineConnectivity client;
	string Username = "";
	string Password = "";
	TreePath prevPath;
	int remoteSnippets = 0;
	
	public MainWindow (): base (Gtk.WindowType.Toplevel)
	{
		Build ();
		login();
		get_code_from_db();
		Console.WriteLine("Debug");
		language = SourceLanguageManager.Default.GetLanguage("c-sharp");
		view = new SourceView(CreateBuffer("","c-sharp"));
		view.ShowLineNumbers = true;
		view.TabWidth = 4;
		view.Editable = true;
		view.Visible = false;
		view.WidthRequest = 50;
		view.FocusOutEvent += new FocusOutEventHandler(viewerfocusout);
		scrolledwindow1.Add(view);
		/* Tree crap */
		TreeViewColumn langsColumn = new TreeViewColumn();
		langsColumn.Title = "";
		CellRendererText langsCell = new CellRendererText();
		langsCell.Editable = true;
		langsCell.Edited += langsCell_Edited;
		langsColumn.PackStart (langsCell, true);
		treeview3.AppendColumn(langsColumn);
		langsColumn.AddAttribute (langsCell, "text", 0);
		ts = new TreeStore(typeof(string));
		foreach (string lang in SourceLanguageManager.Default.LanguageIds) {
			TreeIter iter = ts.AppendValues(lang);
			foreach (Entry entry in entries) {
				if (entry.Lang == lang) {
					ts.AppendValues(iter, entry.Name);
				}
			}
		}
		ts.SetSortColumnId(0,SortType.Ascending);
		treeview3.Model = ts;
		Console.WriteLine("We should be using \"Bitstream Vera Sans Mono\" Make sure you have it!");
		view.ModifyFont(Pango.FontDescription.FromString("Bitstream Vera Sans Mono"));
		// get a reference to the current assembly
        Assembly a = Assembly.GetExecutingAssembly();
		Stream resource = a.GetManifestResourceStream("gnome-dictionary.png");
		trayIcon = new StatusIcon(new Pixbuf(resource));
		trayIcon.Visible = true;
		trayIcon.Activate += delegate { this.Visible = !this.Visible; };
		trayIcon.PopupMenu += OnTrayIconPopup;
		trayIcon.Tooltip = "Snippet Manager!";
	}
	public void login(){
		get_login_info();
		client = new OnlineConnectivity(Username,Password);
		string status = client.LoginCheck();
		if (status.Split(' ')[0] == "OK") { 
			label3.Text = "Logged in as:"+client.Username; 
			remoteSnippets = int.Parse(status.Split(':')[1].Trim()); 
		}
	}
	public void cp2cb(string data) {
		Clipboard cb = Gtk.Clipboard.Get(Gdk.Atom.Intern("CLIPBOARD", true));
		cb.SetText(data);
	}
	static void OnTrayIconPopup (object o, EventArgs args) {
		Menu popupMenu = new Menu();
		ImageMenuItem menuItemQuit = new ImageMenuItem ("Quit");
		Gtk.Image appimg = new Gtk.Image(Stock.Quit, IconSize.Menu);
		menuItemQuit.Image = appimg;
		popupMenu.Add(menuItemQuit);
		// Quit the application when quit has been clicked.
		menuItemQuit.Activated += delegate { Application.Quit(); };
		popupMenu.ShowAll();
		popupMenu.Popup();
	}

	/// <summary>
	/// [Event] Called when a user is attempting to edit an entry. (Makes it so langs CANNOT be renamed)
	/// Also updated any changes into the database
	/// </summary>
	/// <param name="o">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="args">
	/// A <see cref="Gtk.EditedArgs"/>
	/// </param>
	private void langsCell_Edited (object o, Gtk.EditedArgs args)
	{
		/* Only re-edit actual snippet fields */
		Gtk.TreeIter iter;
		ts.GetIter (out iter, new Gtk.TreePath (args.Path));
		if (args.Path.Contains(":")) {
			dbaseconnection("UPDATE Snippets SET Name = '"+args.NewText+"' WHERE Name = '"+(string)ts.GetValue(iter,0)+"'");
			rehash();
		}
	}
	/// <summary>
	/// [Event] called when the main gtksourceview loses focus (Auto saves to database)
	/// </summary>
	/// <param name="o">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="args">
	/// A <see cref="FocusOutEventArgs"/>
	/// </param>
	public void viewerfocusout(object o, FocusOutEventArgs args) {
		savecode();
	}
	/// <summary>
	/// Saved the currently active code in the gtksourceview into the datbase
	/// </summary>
	public void savecode() {
		try{
		    Gtk.TreeIter piter;
			string val = (string)ts.GetValue(activeiter,0);
			//Console.WriteLine("DEBUG: "+val);
			ts.IterParent(out piter, activeiter);
			string owner = (string)ts.GetValue(piter,0);
		    dbaseconnection("UPDATE Snippets SET Code = '"+view.Buffer.Text.Replace("'","''")+"' WHERE Name = '"+val.Replace("'","''")+"' AND Lang = '"+owner.Replace("'","''")+"'");
		    label2.Text = val+" saved!";
			rehash();
		}
		catch (Exception e){ Console.WriteLine("Not saving (Empty Buffer?):"+e.Message); }
	}
	/// <summary>
	/// Clears the entries array and the TreeStore and reloads it form the database
	/// </summary>
	public void rehash() {
		// TODO: fix clean auto extend on rehash
		// Rehash the classes?
		entries.Clear();
		get_code_from_db();
		ts.Clear();
		foreach (string lang in SourceLanguageManager.Default.LanguageIds) {
			TreeIter iter = ts.AppendValues(lang);
			foreach (Entry entry in entries) {
				if (entry.Lang == lang) {
					ts.AppendValues(iter, entry.Name);
				}
			}
		}
		treeview3.ExpandToPath(prevPath);
		//Gtk.TreeIter dsiter;
		//treeview3.Selection.GetSelected(out dsiter);
		//string val = (string)ts.GetValue((Gtk.TreeIter)siter,0);
		//Console.WriteLine("[DEBUGGG] "+val);
		//extend back
		//Gtk.TreeIter piter;
		//if (IsAfterRehashParent())
		//	piter = oldsiter;
		//else
		//	ts.IterParent(out piter, oldsiter);
		//treeview3.ExpandRow(ts.GetPath(piter),true);
	}
	/// <summary>
	/// Checks if the currently selected node is a Parend node or child.
	/// </summary>
	/// <returns>
	/// A <see cref="System.Boolean"/>
	/// </returns>
	public bool IsActiveParent() {
		try {
			if (ts.GetPath(siter).ToString().Contains(":")) {
				return false;
			}
			return true;
		}
		catch (Exception e){ Console.WriteLine("Error checking if IsParent.. Weird?:"+e.Message); }
		return false;
	}
	/// <summary>
	/// BROKEN DONTUSE?!
	/// </summary>
	/// <returns>
	/// A <see cref="System.Boolean"/>
	/// </returns>
	public bool IsAfterRehashParent() {
		try {
			if (ts.GetPath(oldsiter).ToString().Contains(":")) {
				return false;
			}
			return true;
		}
		catch (Exception e){ Console.WriteLine("Error checking if IsParent.. Weird?:"+e.Message); }
		return false;
	}
	
	/// <summary>
	/// Creates a source buffer the {text} for {lang}, for the gtksourceview control
	/// </summary>
	/// <param name="text">
	/// A <see cref="System.String"/>
	/// </param>
	/// <param name="lang">
	/// A <see cref="System.String"/>
	/// </param>
	/// <returns>
	/// A <see cref="SourceBuffer"/>
	/// </returns>
	SourceBuffer CreateBuffer (string text, string lang)
	{
		language = SourceLanguageManager.Default.GetLanguage(lang);
		SourceBuffer buffer = new SourceBuffer(language);
		buffer.HighlightSyntax = true;
		buffer.HighlightMatchingBrackets = true;
		buffer.Text = text;
		return buffer;
	}
	/// <summary>
	/// Called on application exit
	/// </summary>
	/// <param name="sender">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="a">
	/// A <see cref="DeleteEventArgs"/>
	/// </param>
	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{
		Application.Quit ();
		a.RetVal = true;
	}
	/// <summary>
	/// [Event] Called when application resizes among things. (Abit hacky at the moment).
	/// </summary>
	/// <param name="o">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="args">
	/// A <see cref="Gtk.ExposeEventArgs"/>
	/// </param>
	protected virtual void OnExposeEvent (object o, Gtk.ExposeEventArgs args)
	{
		//TODO: Optimise?
		/* I honestly am to tired to find a more optmised way */
		int height,width = 0;
		this.GetSize(out width, out height);
		menubar1.WidthRequest = width;
		scrolledwindow1.HeightRequest = height-88;
		scrolledwindow1.WidthRequest = width-146;
		view.HeightRequest = height-88;
		view.WidthRequest = width-146;
		treeview3.HeightRequest =  height-88;
		//treeview3.WidthRequest = width-view.WidthRequest-2;
		treeview3.WidthRequest = 120;
		// Toolbar?
		scrolledwindow2.WidthRequest = width-3;
		label2.SetUposition((scrolledwindow2.WidthRequest/2)-95,6);
		label3.SetUposition((scrolledwindow2.WidthRequest-145),6);
		//--
		//statusbar1.SetUposition(2, height-22);
		//statusbar1.WidthRequest = width-10;
		label4.SetUposition((scrolledwindow2.WidthRequest/2)-95,height-20);
	}
	/// <summary>
	/// Set the code in the sourceview to the currently selected node.
	/// </summary>
	/// <param name="name">
	/// A <see cref="System.String"/>
	/// </param>
	/// <param name="lang">
	/// A <see cref="System.String"/>
	/// </param>
	public void setcode(string name, string lang) {
		foreach(Entry entry in entries){
			if ((entry.Name == name) && (entry.Lang == lang)) {
				treeview3.Selection.GetSelected(out activeiter);
				view.Buffer = CreateBuffer(entry.Code, lang);
				label2.Text = lang+" editing mode.";
				ActiveName = name;
				ActiveLang = lang;
			}
		}
	}
	/// <summary>
	/// Create a database connection and run the command {sql}
	/// </summary>
	/// <param name="sql">
	/// A <see cref="System.String"/>
	/// </param>
	/// <returns>
	/// A <see cref="IDataReader"/>
	/// </returns>
	public IDataReader dbaseconnection (string sql){
	   string connectionString = "URI=file:/usr/local/lib/snippetmanager/SnippetCode.db,version=3"; /* For the .debs! SVN USERS DO FIX!*/
       IDbConnection dbcon;
       dbcon = (IDbConnection) new SqliteConnection(connectionString);
       dbcon.Open();
	   // http://lists.ximian.com/pipermail/mono-patches/2005-June/059950.html
		//Mono.Data.SqliteClient
	   // ---
       IDbCommand dbcmd = dbcon.CreateCommand();
       Console.WriteLine("DEBUG SQL: "+sql);
       dbcmd.CommandText = sql;
		dbcmd.Prepare();
       IDataReader reader = dbcmd.ExecuteReader();
	
	   return reader;
	}
	/// <summary>
	/// [Event] Called when the user has selected another node.
	/// </summary>
	/// <param name="sender">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="e">
	/// A <see cref="System.EventArgs"/>
	/// </param>
	protected virtual void OnTreeview3CursorChanged (object sender, System.EventArgs e)
	{
		try {
			string owner = "";
			Gtk.TreeIter zpiter;
			oldsiter = siter;
			treeview3.Selection.GetSelected(out siter);
			string val = (string)ts.GetValue(siter,0);
			view.Visible = false;
			ActiveName = val;
			if (!IsActiveParent()) { 
					ts.IterParent(out zpiter, siter);
					owner = (string)ts.GetValue(zpiter,0);
					view.Visible = true;
					setcode(val,owner);
				ActiveName = val;
				ActiveLang = owner;
			}
			Console.WriteLine("[Debug] Currently Active: "+val+" "+owner); 
			prevPath = ts.GetPath(oldsiter);
		}
		catch (Exception z){
			Console.WriteLine("Unexpected Error: "+z.Message);
		}
		finally {
			if (view.Visible) { button3.Show(); }
			if (!view.Visible) { button3.Hide(); }
		}
	}
	/// <summary>
	/// Grabs all the code from the database and chucks it into the entries array
	/// </summary>
	public void get_code_from_db(){
       IDataReader reader = dbaseconnection("SELECT Name, Lang, Code FROM Snippets");
       while(reader.Read()) {
            string Name = reader.GetString (0);
            string Lang = reader.GetString (1);
			string Code = reader.GetString (2);
			Entry entry = new Entry(Name,Lang,Code);
			entries.Add(entry);
       }
		label4.Text = entries.Count+" entries in the local database("+remoteSnippets.ToString()+" globally)";
	}
	/// <summary>
	/// Grabs the users login info from the database
	/// </summary>
	public void get_login_info(){
		IDataReader reader = dbaseconnection("SELECT username, password FROM userinfo");
		while (reader.Read()) {
			Username = reader.GetString(0);
			Password = reader.GetString(1);
		}
	}
	protected virtual void OnSaveActionActivated (object sender, System.EventArgs e)
	{
		savecode();
	}
	/// <summary>
	/// Add a new Child node (Snippet)
	/// </summary>
	public void AddField() {
		/* Cheap way to see if we are a lang node */
		if (IsActiveParent()) {
			string val = (string)ts.GetValue(siter,0);
		    dbaseconnection("INSERT INTO Snippets (Name, Lang, Code) values ('','"+val+"','')");
			label2.Text = "created new entry in "+val;
			rehash();
		}
		else {
			Gtk.TreeIter piter;
			ts.IterParent(out piter, siter);
			string owner = (string)ts.GetValue(piter,0);
			label2.Text = "created new entry in "+owner;
		    dbaseconnection("INSERT INTO Snippets (Name, Lang, Code) values ('','"+owner+"','')");
			rehash();
		}
	}
	/// <summary>
	/// Delete a child node (Snippet)
	/// </summary>
	public void DelField() {
		/* Cheap way to see if we are a lang node */
		if (IsActiveParent()) { }
		else {
			Gtk.TreeIter piter;
			string val = (string)ts.GetValue(siter,0);
			ts.IterParent(out piter, siter);
			string owner = (string)ts.GetValue(piter,0);
			dbaseconnection("DELETE FROM Snippets WHERE Name = '"+val+"' AND Lang = '"+owner+"'");
			label2.Text = val+"[in:"+owner+"] deleted.";
			rehash();
			view.Visible = false;
		}
	}
	protected virtual void OnTreeview3KeyPressEvent (object o, Gtk.KeyPressEventArgs args)
	{
		if (args.Event.KeyValue == 65379)// INS
			AddField();
		else if (args.Event.KeyValue == 65535)// DEL
			DelField();
	}
	// Menu+Button Operations.
	protected virtual void OnAddActionActivated (object sender, System.EventArgs e) { AddField(); }
	protected virtual void OnDelActionActivated (object sender, System.EventArgs e){ DelField(); }
	protected virtual void OnExitActionActivated (object sender, System.EventArgs e){ Application.Quit(); }
	protected virtual void OnButton3Clicked (object sender, System.EventArgs e){ savecode(); }
	protected virtual void OnButton2Clicked (object sender, System.EventArgs e){ DelField(); }
	protected virtual void OnButton1Clicked (object sender, System.EventArgs e){ AddField(); }
	/// <summary>
	/// [Event] Called when the user is attempting to upload a snippet.
	/// </summary>
	/// <param name="sender">
	/// A <see cref="System.Object"/>
	/// </param>
	/// <param name="e">
	/// A <see cref="System.EventArgs"/>
	/// </param>
	protected virtual void OnButton4Clicked (object sender, System.EventArgs e)
	{
		if (view.Visible) {
			foreach (Entry entry in entries) {
				if ((entry.Name == ActiveName) && (entry.Lang == ActiveLang)) { // Helps to be sure?!
					if (client.UploadSnippet(entry) == "OK") {
						label2.Text = "Uploaded "+ActiveName+" to your repo!";
					}
				}
			}
		}
	}

	protected virtual void OnAboutActionActivated (object sender, System.EventArgs e)
	{
		AboutDialog dialog = new AboutDialog ();
	
		Assembly asm = Assembly.GetExecutingAssembly ();
		
		dialog.Name = (asm.GetCustomAttributes (
			typeof (AssemblyTitleAttribute), false) [0]
			as AssemblyTitleAttribute).Title;
		
		dialog.Version = asm.GetName ().Version.ToString ();
		
		dialog.Comments = (asm.GetCustomAttributes (
			typeof (AssemblyDescriptionAttribute), false) [0]
			as AssemblyDescriptionAttribute).Description;
		
		dialog.Copyright = (asm.GetCustomAttributes (
			typeof (AssemblyCopyrightAttribute), false) [0]
			as AssemblyCopyrightAttribute).Copyright;
		dialog.Authors = authors;
		dialog.License = license;
		dialog.Run();
	}

	protected virtual void OnConfigurationActionActivated (object sender, System.EventArgs e)
	{
		snippetmanager.Configuration cfg = new snippetmanager.Configuration();
		cfg.Show();		
	}
		private static string [] authors = new string [] {
		"Ashley Towns <ikex@orioncode.org>" };
	
		private static string license =
	@"
	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	""Software""), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
}